"""
1、变量作用域：
    变量作用域指的是变量生效的范围，主要分为两类： 局部变量和全局变量
    局部变量：
        所谓局部变量 是定义在函数体内部的变量，即只在函数体内部生效
    全局变量：
        所谓全局变量 指的是在函数体内、外都能生效的变量
"""


# 1.1、局部变量：
def testA():
    a = 100
    print(a)


testA()  # 100
# print(a)  # 变量a是定义在testA函数内的变量，在函数外部访问则立即报错

# 1.2、全局变量：
# 思考：如果有一个数据，在函数A和函数B中都要调用，该怎么办？

# 定义全局变量a
a = 100


def testB():
    print(a)  # 访问全局变量a，并打印变量a存储的数据


testB()


def testC():
    print(a)  # 访问全局变量a，并打印变量a存储的数据


testC()


# 思考：如果testB的函数需求修改变量a的值为200，如何修改？
def testB():
    a = 200  # 如果只是想单纯的将testB修改成200 ，直接将变量添加到函数内部，使用局部变量即可
    print(a)


testB()


# 思考：如何在函数体内部修改全局变量？
# 使用global 定义全局变量，PS：global只在定义后的函数生效
# 定义全局变量b
glo_num = 100

def testA():
    print(glo_num)  # global实在testB修改的，所以这个不受影响


testA()     # 100


def testB():
    # global 关键字声明b是全局变量
    global glo_num
    glo_num = 200
    print(glo_num)  # 访问修改过后的全局变量b，并打印变量b存储的数据


testB()     # 200


def testC():
    print(glo_num + 1)  # 访问修改过后的全局变量b，并打印变量b存储的数据


testC()     # 201

# ===================================================================================

"""
2、多函数程序执行流程：
    一般在实际开发过程中，一个程序往往由多个函数组成，并且多个函数共享某些数据，如下：
        · 共享全局变量
        · 返回值作为参数传递
"""

# 2.1、共享全局变量

# 定义全局变量glo_num
glo_num = 0

def test():
    # global 关键字声明glo_num是全局变量，并修改glo_num的返回值
    global glo_num
    glo_num = 200
    print(glo_num)  # 访问修改过后的全局变量b，并打印变量b存储的数据


test()  # 200


def test2():
    print(glo_num)  # 访问修改过后的全局变量b，并打印变量b存储的数据


test2()

# 2.2、返回值作为参数传递

def fn1():
    return 50


def fn2(num):
    print(num)


fn2(fn1())  # 将函数返回值(fn1)所在变量作为参数传递到fn2函数

# ===================================================================================

"""
3、函数的返回值
"""
# 思考：如果一个函数写了两个return 程序该如何执行？
def return_num():
    return 1
    return 2


# 只执行了第一个return，原因是因为return可以退出当前函数，导致return下方的代码均不执行
print(return_num())


# 思考：那如果一个函数要有多个返回值，该如何书写代码呢？
def n1():
    return 1, 2, 3, "嘿嘿"


"""return 返回多个返回值时，默认是以元祖类型，当然后面还可以跟列表、字典、元组"""
print(n1())


# 字典
def n2():
    return {1, 2, 3, "嘿嘿"}


print(n2())


# 列表
def n3():
    return [1, 2, 3, "嘿嘿"]


print(n3())

# ===================================================================================

"""
3、函数的参数：
    · 位置参数：
        调用函数时，根据函数定义的参数位置来传递参数。
            PS：传递和定义参数的顺序及个数必须保持一致
    · 关键字参数：
            函数调用，通过“键=值”的形式加以指定，可以让函数更加清晰、容易使用，。同时也清除了参数的顺序需求。
            PS：函数调用时，如果有位置参数时，位置参数必须在关键字参数的前面，但关键字参数值之间不存在先后顺序。
    · 缺省参数：
        缺省参数也叫默认参数，用于定义函数，为参数提供默认值，调用函数时可不传递默认参数的值。
            PS：所有位置参数必须出现在默认参数前，包含函数定义和调用。
    · 不定长参数
"""

# 定义阶段&默认参数
def user_info(name,age,sex, country="中国"):
    print(f"你的名字是：{name}，你的年龄是：{age}，你的性别是：{sex}，国籍是：{country}")

# 3.1、位置参数：传递和定义参数的顺序及个数必须保持一致
user_info("小明",20,"男","中国")

# 3.2、关键字参数：函数调用时，如果有位置参数时，位置参数必须在关键字参数的前面，但关键字参数值之间不存在先后顺序。
# 关键字后面
user_info("小丽", age=18, sex="女",country="都是中国的")    # 上述定义了默认的参数country="中国"，但是默认参数是可以修改的，。

# 3.3、缺省参数：也叫默认参数，所有位置参数必须出现在默认参数前，包含函数定义和调用。
user_info("小蓝",19,"女")      # 这个没有传country字段，但是也给出了结果，因为上面已经定义了默认的字段，也就是上述有定义默认参数时，不传此参数也可以。\

# ===================================================================================

"""
4、不定长参数传递：
    不定长参数也叫可变参数，用于不确定调用的时候，会传递多少个参数(不传也可以)的场景。此时，可用包裹(packing)位置参数，或者包裹关键字参数，来进行参数传递，会显得非常方便
    · 包裹位置传递：
        PS：传进的所有参数都会被args变量收集，她会根据传进参数的位置合并为一个元组(tuple)，args是元组类型，这就是包裹位置传递
    · 包裹关键字传递：
"""

# 4.1、包裹位置参数传递(元组)：传进的所有参数都会被args变量收集，她会根据传进参数的位置合并为一个元组
def user_info(*args):
    print(args)

user_info("小红", 18, "是个妹子")

# 提前设置好的数据传递时，前面要加一个*,不然的话就会以一个你所定义的类型传入，比如说下面这个列表
msg = ["秀儿", 21, "是个妹子"]
user_info(msg)
msg2 = {"秀儿", 21, "是个妹子"}
user_info(*msg2)

# 4.2、包裹关键字参数传递(字典)
def user_info2(**kwargs):
    print(kwargs)


user_info2(name="小刚", age=22, sex="男", height=179)

# 提前设置好的数据传递时，前面要加两个**，且传入的Key必须是字符串，不然就会报错。
ms = {'name': '小新', 'age': 22, 'sex': '男', 'height': 176}
user_info2(**ms)

# ===================================================================================

"""
5、拆包和交换变量值：
    · 拆包：
        元组
        字典
    · 交换变量值
"""

# 5.1、拆包
def fn():
    return 100,200

n,m = fn()
print(n)
print(m)

# 5.2、字典：对字典拆包，取出来的是字典的key，如果想取value，则需要加.values()
dict1 = {"name": "花儿", "age": 18}
a, b = dict1
# 取出来的是字典的key
print(a)
print(b)

# 如果想取value，则需要加.values()
aa, bb = dict1.values()
print(aa)
print(bb)

# 5.2、交换变量值
# 定义变量值
a = 1
b = 2
# 交换变量值
a, b = b, a
print(a)
print(b)

# ===================================================================================

"""
6、引用：
    · 了解引用：
        在Python中，值是靠引用来传递的
        我们可以用id()来判断两个变量是否为同一个值的引用，我们可以将id值理解为那块内存的地址标识
    · 引用当做实参
"""

# 6.1、了解引用
# int类型
a = 1
b = a
print(b)
# 他们的id都是一样的
print(id(a))    # 2448765446448
print(id(b))    # 2448765446448

a = 2
print(b)    # 结果还是上面的那个1，实名int类型为不可变类型
print(id(b))    # 2448765446448
print(id(a))    # 2448765446480  此时得到的数据是2的内存地址

# 列表
aa = [10, 20]
bb = aa
print(id(aa))   # 1982593315456
print(id(bb))   # 1982593315456

aa.append(30)
print(bb)       # [10, 20, 30]，id还都是一样的，说明列表是可变的数据类型
print(id(aa))   # 1982593315456
print(id(bb))   # 1982593315456

# 6.2、引用当实参
def test(a):
    print(a)   # 输出传入的参数值 100
    print(id(a))    # 输出参数a的内存地址  id：2063400261072
    a += a      # 对于不可变对象，这会创建一个新的对象
    print(a)    # 输出 200
    print(id(a))    # 输出新对象的内存地址，与上面那个不同  id：2063400264336

# 在Python中，整数是不可变对象，当执行a +=a时，实际上创建了一个新的整数对象，并将a重新指向这个新对象，所以，前后a 的id会不同
b = 100
test(b)
# ===================================================================================

"""
7、可变数据类型和不可变数据类型：
    所谓可变数据类型与不可变数据类型指的是：数据能够直接进行修改，如果能直接修改，那么就是可变，否则就是不可变
    可变数据类型：
        列表、字典、集合
    不可变数据类型：
        整数、浮点型、字符串、元组、布尔值
"""